/*
 * OutputStream.java
 * 
 * This file is part of Netuno Psamata.
 * 
 * CajuScript is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3, or (at your option) 
 * any later version.
 * 
 * CajuScript is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with CajuScript.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.netuno.psamata.io;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
 * Output Stream.
 * @author eduveks
 */
public class OutputStream extends java.io.OutputStream {
    /**
     * Write output is enabled or not?
     */
    private boolean enabled = true;
    /**
     * Is first writer.
     */
    private boolean isFirst = true;
    /**
     * Starting?
     */
    private boolean start = true;
    /**
     * Output.
     */
    private java.io.OutputStream out = null;
    /**
     * Output Stream Notify.
     */
    private OutputStreamNotify notify = null;
    private List<java.io.OutputStream> mirrors = new ArrayList<java.io.OutputStream>();
    private int length = 0;
    /**
     * Output Stream.
     * @param output OutputStream
     */
    public OutputStream(final java.io.OutputStream output) {
        out = output;
    }
    /**
     * Set Notify.
     * @param outputNotify Output Stream Notify
     */
    public final void setNotify(final OutputStreamNotify outputNotify) {
        notify = outputNotify;
    }

    public OutputStreamNotify getNotify() {
        return notify;
    }

    public List<java.io.OutputStream> getMirrors() {
        return mirrors;
    }

    /**
     * Start.
     */
    public final void start() {
        if (notify != null && isFirst && start) {
            isFirst = false;
            notify.start();
        }
    }
    /**
     * Set Start.
     * @param v Start
     */
    public final void setStart(final boolean v) {
        this.start = v;
    }
    /**
     * Finish.
     */
    public final void finish() {
        if (notify != null) {
            notify.finish();
        }
    }

    /**
     * If the output write is enabled or not.
     * @return Is enabled?
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * Set if the output write is enabled or not.
     * @param enabled Is enabled?
     */
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * Print, write string.
     * @param bytes String
     * @throws IOException Write exception
     */
    public final void print(final String bytes) throws IOException {
        writeBytes(bytes);
    }
    /**
     * Print, write integer.
     * @param v Integer
     * @throws IOException Write exception
     */
    public final void print(final int v) throws IOException {
        writeBytes(Integer.toString(v));
    }
    /**
     * Print, write float.
     * @param v Float
     * @throws IOException Write exception
     */
    public final void print(final float v) throws IOException {
        String value = "" + v;
        if (value.endsWith(".0")) {
            writeBytes(""+ (int)v);
        } else {
            writeBytes(value);
        }
    }
    /**
     * Print, write double.
     * @param v Double
     * @throws IOException Write exception
     */
    public final void print(final double v) throws IOException {
        String value = "" + v;
        if (value.endsWith(".0")) {
            writeBytes(""+ (int)v);
        } else {
            writeBytes(value);
        }
    }
    /**
     * Print, write long.
     * @param v Long
     * @throws IOException Write exception
     */
    public final void print(final long v) throws IOException {
        writeBytes(Long.toString(v));
    }
    /**
     * Print, write char.
     * @param v Char
     * @throws IOException Write exception
     */
    public final void print(final char v) throws IOException {
        writeBytes(Character.toString(v));
    }
    /**
     * Print, write boolean.
     * @param v Boolean
     * @throws IOException Write exception
     */
    public final void print(final boolean v) throws IOException {
        writeBytes(Boolean.toString(v));
    }
    /**
     * Write a line brake.
     * @throws IOException Write exception
     */
    public final void println() throws IOException {
        writeBytes("\n");
    }
    /**
     * Write String line.
     * @param bytes Text of the line
     * @throws IOException Write exception
     */
    public final void println(final String bytes) throws IOException {
        writeBytes(bytes + "\n");
    }
    /**
     * Write bytes array.
     * @param bytes Data
     * @param off Start offset
     * @param leng Number of bytes to sending
     * @throws IOException Write exception
     */
    @Override
    public final void write(final byte[] bytes, final int off, final int leng)
    throws IOException {
        start();
        if (!isEnabled()) {
            return;
        }
        for (java.io.OutputStream mirror : mirrors) {
            mirror.write(bytes, off, leng);
        }
        out.write(bytes, off, leng);
        length += leng;
    }
    /**
     * Write bytes array.
     * @param b bytes array to write
     * @throws IOException Write exception
     */
    @Override
    public final void write(final byte[] b) throws IOException {
        start();
        if (!isEnabled()) {
            return;
        }
        for (java.io.OutputStream mirror : mirrors) {
            mirror.write(b);
        }
        out.write(b);
        length += b.length;
    }
    /**
     * Write int.
     * @param b int to write
     * @throws IOException Write exception
     */
    @Override
    public final void write(final int b) throws IOException {
        start();
        if (!isEnabled()) {
            return;
        }
        for (java.io.OutputStream mirror : mirrors) {
            mirror.write(b);
        }
        out.write(b);
        length++;
    }
    /**
     * Write byte int.
     * @param b int to write
     * @throws IOException Write exception
     */
    public final void writeByte(final int b) throws IOException {
        start();
        if (!isEnabled()) {
            return;
        }
        for (java.io.OutputStream mirror : mirrors) {
            mirror.write(b);
        }
        out.write(b);
        length++;
    }
    /**
     * Write boolean.
     * @param v boolean to write
     * @throws IOException Write exception
     */
    public final void writeBoolean(final boolean v) throws IOException {
        start();
        if (!isEnabled()) {
            return;
        }
        for (java.io.OutputStream mirror : mirrors) {
            mirror.write(v ? 1 : 0);
        }
        out.write(v ? 1 : 0);
        length++;
    }
    /**
     * Write short.
     * @param v short to write
     * @throws IOException Write exception
     */
    public final void writeShort(final int v) throws IOException {
        start();
        if (!isEnabled()) {
            return;
        }
        for (java.io.OutputStream mirror : mirrors) {
            mirror.write((v >>> 8) & 0xFF);
            mirror.write((v >>> 0) & 0xFF);
        }
        out.write((v >>> 8) & 0xFF);
        length++;
        out.write((v >>> 0) & 0xFF);
        length++;
    }
    /**
     * Write char.
     * @param v char to write
     * @throws IOException Write exception
     */
    public final void writeChar(final int v) throws IOException {
        start();
        if (!isEnabled()) {
            return;
        }
        for (java.io.OutputStream mirror : mirrors) {
            mirror.write((v >>> 8) & 0xFF);
            mirror.write((v >>> 0) & 0xFF);
        }
        out.write((v >>> 8) & 0xFF);
        length++;
        out.write((v >>> 0) & 0xFF);
        length++;
    }
    /**
     * Write int.
     * @param v int to write
     * @throws IOException Write exception
     */
    public final void writeInt(final int v) throws IOException {
        start();
        if (!isEnabled()) {
            return;
        }
        for (java.io.OutputStream mirror : mirrors) {
            mirror.write((v >>> 24) & 0xFF);
            mirror.write((v >>> 16) & 0xFF);
            mirror.write((v >>>  8) & 0xFF);
            mirror.write((v >>>  0) & 0xFF);
        }
        out.write((v >>> 24) & 0xFF);
        length++;
        out.write((v >>> 16) & 0xFF);
        length++;
        out.write((v >>>  8) & 0xFF);
        length++;
        out.write((v >>>  0) & 0xFF);
        length++;
    }
    private byte writeBuffer[] = new byte[8];
    /**
     * Write long.
     * @param v long to write
     * @throws IOException Write exception
     */
    public final void writeLong(final long v) throws IOException {
        start();
        if (!isEnabled()) {
            return;
        }
        writeBuffer[0] = (byte)(v >>> 56);
        writeBuffer[1] = (byte)(v >>> 48);
        writeBuffer[2] = (byte)(v >>> 40);
        writeBuffer[3] = (byte)(v >>> 32);
        writeBuffer[4] = (byte)(v >>> 24);
        writeBuffer[5] = (byte)(v >>> 16);
        writeBuffer[6] = (byte)(v >>>  8);
        writeBuffer[7] = (byte)(v >>>  0);
        for (java.io.OutputStream mirror : mirrors) {
            mirror.write(writeBuffer, 0, 8);
        }
        out.write(writeBuffer, 0, 8);
        length += 8;
    }
    /**
     * Write float.
     * @param v float to write
     * @throws IOException Write exception
     */
    public final void writeFloat(final float v) throws IOException {
        start();
        writeInt(Float.floatToIntBits(v));
    }
    /**
     * Write double.
     * @param v double to write
     * @throws IOException Write exception
     */
    public final void writeDouble(final double v) throws IOException {
        start();
        writeLong(Double.doubleToLongBits(v));
    }
    /**
     * Write string bytes.
     * @param s String to write
     * @throws IOException Write exception
     */
    public final void writeBytes(final String s) throws IOException {
        start();
        if (!isEnabled()) {
            return;
        }
        int len = s.length();
        for (int i = 0 ; i < len ; i++) {
            for (java.io.OutputStream mirror : mirrors) {
                mirror.write((byte)s.charAt(i));
            }
            out.write((byte)s.charAt(i));
            length++;
        }
    }
    /**
     * Write string chars.
     * @param s String to write
     * @throws IOException Write exception
     */
    public final void writeChars(final String s) throws IOException {
        start();
        if (!isEnabled()) {
            return;
        }
        int len = s.length();
        for (int i = 0 ; i < len ; i++) {
            int v = s.charAt(i);
            for (java.io.OutputStream mirror : mirrors) {
                mirror.write((v >>> 8) & 0xFF);
                mirror.write((v >>> 0) & 0xFF);
            }
            out.write((v >>> 8) & 0xFF);
            length++;
            out.write((v >>> 0) & 0xFF);
            length++;
        }
    }
    
    public final void writeFile(String file) throws IOException {
    	writeFile(new java.io.File(file));
    }
    
    public final void writeFile(java.io.File file) throws IOException {
    	ReadableByteChannel channelIn = Channels.newChannel(new java.io.FileInputStream(file));
    	try {
	    	WritableByteChannel channelOut = Channels.newChannel(out);
	        ByteBuffer buf = ByteBuffer.allocate(1048576);
	        while (channelIn.read(buf) <= 0) {
	        	channelOut.write(buf);
	        }
    	} finally {
    		channelIn.close();
    	}
    }

    /**
     * Get length
     * @return length
     */
    public int getLength() {
        return length;
    }

    /**
     * Flush.
     * @throws IOException Flush exception
     */
    @Override
    public final void flush() throws IOException {
        if (!isEnabled()) {
            return;
        }
        for (java.io.OutputStream mirror : mirrors) {
            mirror.flush();
        }
        out.flush();
    }
    /**
     * Close.
     * @throws IOException Close exception
     */
    @Override
    public final void close() throws IOException {
        if (!isEnabled()) {
            return;
        }
        for (java.io.OutputStream mirror : mirrors) {
            mirror.close();
        }
        out.close();
    }

    public static void writeToFile(byte[] bytes, java.io.File path, boolean append) throws FileNotFoundException, IOException {
        FileOutputStream outputFile = null;
        try {
            outputFile = new FileOutputStream(path, append);
            FileChannel outChannel = outputFile.getChannel();
            outChannel.write(ByteBuffer.wrap(bytes));
            outChannel.close();
        } finally {
            if (outputFile != null) {
                outputFile.close();
            }
        }
    }

    public static void writeToFile(byte[] bytes, String path, boolean append) throws FileNotFoundException, IOException {
        writeToFile(bytes, new java.io.File(path), append);
    }
    
    public static void writeToFile(String content, java.io.File path, boolean append) throws FileNotFoundException, IOException {
        FileOutputStream outputFile = null;
        try {
            outputFile = new FileOutputStream(path, append);
            FileChannel outChannel = outputFile.getChannel();
            outChannel.write(ByteBuffer.wrap(content.getBytes()));
            outChannel.close();
        } finally {
            if (outputFile != null) {
                outputFile.close();
            }
        }
    }

    public static void writeToFile(String content, String path, boolean append) throws FileNotFoundException, IOException {
        writeToFile(content, new java.io.File(path), append);
    }
}
